//
// Created by EmFi on 2023/5/23.
//

#include <iostream>
#include "Database/Point.h"

using namespace std;

class UI {
private:
    TransactionalDatabase *db = nullptr;
    bool checkOpen() {
        if (db == nullptr) {
            cout << "Database not opened." << endl;
            return false;
        }
        return true;
    }
public:
    virtual ~UI() {
        delete this->db;
    }

    int menu() {
        cout << "------------------------" << endl;
        cout << "|       D B M S        |" << endl;
        cout << "------------------------" << endl;
        cout << "| 1. Open database     |" << endl;
        cout << "| 2. Import CSV        |" << endl;
        cout << "| 3. Insert            |" << endl;
        cout << "| 4. Update            |" << endl;
        cout << "| 5. Delete            |" << endl;
        cout << "| 6. Select            |" << endl;
        cout << "| 7. Select All        |" << endl;
        cout << "| 8. Query (non-index) |" << endl;
        cout << "| 9. Query (use-index) |" << endl;
        cout << "| 10. List transaction |" << endl;
        cout << "| 11. Rollback         |" << endl;
        cout << "| 12. Clear database   |" << endl;
        cout << "| 13. Close database   |" << endl;
        cout << "| 14. Delete database  |" << endl;
        cout << "| 0. Exit              |" << endl;
        cout << "------------------------" << endl;
        cout << "Input option: ";
        int option;
        cin >> option;
        return option;
    }

    void openDatabase() {
        if (db != nullptr) {
            cout << "Database opened." << endl;
            return;
        }
        cout << "File name to open: ";
        string fn;
        cin >> fn;
        this->db = new TransactionalDatabase(fn);
    }

    void importCsv() {
        if (!this->checkOpen()) {
            return;
        }
        cout << "File name to import: ";
        string fn;
        cin >> fn;
        list<Point> read = CsvUtil::read(fn);
        if (!FillUtil::checkEmpty(read)) {
            FillUtil::fill(read);
        }
        if (!FillUtil::checkDuplicate(read)) {
            return;
        }
        db->insert(read);
    }

    void insert() {
        if (!this->checkOpen()) {
            return;
        }
        cout << "Input data: " << endl;
        Point point;
        double num;
        cout << "x: ";
        cin >> num;
        point.push(num);
        cout << "y: ";
        cin >> num;
        point.push(num);
        cout << "z: ";
        cin >> num;
        point.push(num);
        cout << "w: ";
        cin >> num;
        point.push(num);
        int index = db->insert(point);
        cout << "New index: " << index << endl;
    }

    void update() {
        if (!this->checkOpen()) {
            return;
        }
        cout << "Input index: ";
        int index;
        cin >> index;
        Point point;
        double num;
        cout << "new x: ";
        cin >> num;
        point.push(num);
        cout << "new y: ";
        cin >> num;
        point.push(num);
        cout << "new z: ";
        cin >> num;
        point.push(num);
        cout << "new w: ";
        cin >> num;
        point.push(num);
        db->update(index, point);
    }

    void remove() {
        if (!this->checkOpen()) {
            return;
        }
        cout << "Input index: ";
        int index;
        cin >> index;
        db->remove(index);
    }

    void select() {
        if (!this->checkOpen()) {
            return;
        }
        cout << "Input index: ";
        int index;
        cin >> index;
        cout << db->select(index) << endl;
    }

    void selectAll() {
        if (!this->checkOpen()) {
            return;
        }
        list<Point> list = db->selectAll();
        for (auto &it : list) {
            cout << it << endl;
        }
    }

    void legacyQuery() {
        if (!this->checkOpen()) {
            return;
        }
        double xMin, xMax, yMin, yMax, zMin, zMaz, wMin, wMax;
        cout << "x-Min: ";
        cin >> xMin;
        cout << "x-Max: ";
        cin >> xMax;
        cout << "y-Min: ";
        cin >> yMin;
        cout << "y-Max: ";
        cin >> yMax;
        cout << "z-Min: ";
        cin >> zMin;
        cout << "z-Max: ";
        cin >> zMaz;
        cout << "w-Min: ";
        cin >> wMin;
        cout << "w-Max: ";
        cin >> wMax;
        list<Point> list = db->legacyQuery(xMin, xMax, yMin, yMax, zMin, zMaz, wMin, wMax);
        for (auto &it : list) {
            cout << it << endl;
        }
    }

    void query() {
        if (!this->checkOpen()) {
            return;
        }
        double xMin, xMax, yMin, yMax, zMin, zMaz, wMin, wMax;
        cout << "x-Min: ";
        cin >> xMin;
        cout << "x-Max: ";
        cin >> xMax;
        cout << "y-Min: ";
        cin >> yMin;
        cout << "y-Max: ";
        cin >> yMax;
        cout << "z-Min: ";
        cin >> zMin;
        cout << "z-Max: ";
        cin >> zMaz;
        cout << "w-Min: ";
        cin >> wMin;
        cout << "w-Max: ";
        cin >> wMax;
        list<Point> list = db->query(xMin, xMax, yMin, yMax, zMin, zMaz, wMin, wMax);
        for (auto &it : list) {
            cout << it << endl;
        }
    }

    void listTransaction() {
        if (!this->checkOpen()) {
            return;
        }
        db->printTransaction();
    }

    void rollback() {
        if (!this->checkOpen()) {
            return;
        }
        cout << "Input transaction index: ";
        int index;
        cin >> index;
        db->rollback(index);
    }

    void clear() {
        if (!this->checkOpen()) {
            return;
        }
        db->clear();
    }

    void closeDatabase() {
        if (!this->checkOpen()) {
            return;
        }
        delete this->db;
        this->db = nullptr;
    }

    void deleteDatabase() {
        if (db != nullptr) {
            cout << "Close database first." << endl;
            return;
        }
        cout << "File name to delete: ";
        string fn;
        cin >> fn;
        ifstream test(fn);
        if (!test.is_open()) {
            cout << "Database not exists." << endl;
            return;
        }
        test.close();
        ::remove(fn.c_str());
        ::remove((fn + ".0.index").c_str());
        ::remove((fn + ".1.index").c_str());
        ::remove((fn + ".2.index").c_str());
        ::remove((fn + ".3.index").c_str());
        ::remove((fn + ".trans").c_str());
    }
};
